package com.atguigu.gmall.product.service.impl;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.atguigu.gmall.product.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String lock = "lock";


    //private static String lock_valu = "lock";

    /**
     * 采用分布式锁控制方法,同一时间只能有一个线程来访问.
     * 解决:高并发请求下缓存击穿问题
     */
    //@Override
    //public void testLock() {
    //    //1.尝试获取分布式锁  获取到分布式锁以后 再执行业务逻辑 设置锁过期时间
    //    String lock_valu = UUID.randomUUID().toString().replaceAll("-", "");
    //    Boolean flag = redisTemplate.opsForValue().setIfAbsent(lock, lock_valu, 5, TimeUnit.SECONDS);
    //    if (flag) {
    //        try {
    //            //2.业务逻辑: 查询Redis中的num值
    //            String value = (String) this.redisTemplate.opsForValue().get("num");
    //
    //            // 没有该值return 判断字符串采用apache common包中工具类
    //            if (StringUtils.isBlank(value)) {
    //                return;
    //            }
    //            // 有值就转成成int
    //            int num = Integer.parseInt(value);
    //            // 把Redis中的num值+1
    //            this.redisTemplate.opsForValue().set("num", String.valueOf(++num));
    //        } catch (NumberFormatException e) {
    //            throw new RuntimeException(e);
    //        } finally {
    //            //3.释放 判断当前redis中锁的值 跟 线程锁的值是否一致  判断 跟 删除   是原子操作吗? 不是原子操作 怎么保证原子性 采用Lua脚本
    //            //String redisLock = redisTemplate.opsForValue().get(lock);
    //            //if (lock_valu.equals(redisLock)) {
    //            //    redisTemplate.delete(lock);
    //            //}
    //
    //            //4.SpringDataRedis如何执行lua脚本
    //            //4.1 创建脚本对象 封装lua脚本语言  KEYS[1]:传入锁的名称  ARGV[1]:传入当前线程锁的uuid值
    //            String luaText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
    //                    "then\n" +
    //                    "    return redis.call(\"del\",KEYS[1])\n" +
    //                    "else\n" +
    //                    "    return 0\n" +
    //                    "end";
    //            DefaultRedisScript<Long> script = new DefaultRedisScript<>();
    //            script.setScriptText(luaText);
    //            script.setResultType(Long.class);
    //            //4.2 为脚本中动态占位参数设置值
    //            //4.3 执行lua脚本
    //            Long result = redisTemplate.execute(script, Arrays.asList(lock), new String[]{lock_valu});
    //            System.out.println(result);
    //        }
    //    } else {
    //        try {
    //            TimeUnit.MILLISECONDS.sleep(500);
    //            log.error("获取分布式锁失败,尝试下次获取");
    //        } catch (InterruptedException e) {
    //            throw new RuntimeException(e);
    //        }
    //        //获取锁失败,进行自旋,再次尝试
    //        this.testLock();
    //    }
    //}

    @Autowired
    private RedissonClient redissonClient;


    /**
     * 使用Redisson实现分布式锁
     */
    @Override
    public void testLock() {
        //1.创建锁对象
        RLock rLock = redissonClient.getLock("lock");
        try {
            //1.1 获取锁-线程一直等到获取锁为止  lock方法 默认锁时间为30秒,如果任务执行超时,启动"看门狗"线程,检查业务执行情况,如果业务在执行,自动为锁续期
            //rLock.lock();
            //1.2 获取锁,设置锁的过期时间,到时间锁自动释放
            //rLock.lock(5, TimeUnit.SECONDS);
            //1.3 尝试获取锁 参数1:获取锁等待时间 超过该时间没有获取锁 则返回false 参数2:锁过期时间 参数3:时间单位
            boolean flag = rLock.tryLock(1, 5, TimeUnit.SECONDS);
            if (flag) {
                //2.业务逻辑: 查询Redis中的num值
                String value = (String) this.redisTemplate.opsForValue().get("num");

                // 没有该值return 判断字符串采用apache common包中工具类
                if (StringUtils.isBlank(value)) {
                    return;
                }
                // 有值就转成成int
                int num = Integer.parseInt(value);
                // 把Redis中的num值+1
                this.redisTemplate.opsForValue().set("num", String.valueOf(++num));

                //测试可重入
                this.check();
            } else {
                //如果场景 要求线程业务必须都执行 自旋
                TimeUnit.SECONDS.sleep(1);
                this.testLock();
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //3.释放锁
            rLock.unlock();
        }
    }


    private void check() {
        RLock rLock = redissonClient.getLock("lock");
        try {
            rLock.lock();
            System.out.println("测试可重入");

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            rLock.unlock();
        }
    }


    /**
     * 读数据方法
     *
     * @return
     */
    @Override
    public String read() {
        //1.获取读锁
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("rwLock");
        RLock rLock = rwLock.readLock();
        rLock.lock(5, TimeUnit.SECONDS);
        //2.执行读数据
        System.out.println("执行读数据操作");
        //3.将锁释放 todo为了模拟并发读写 锁情况
        return null;
    }

    /**
     * 写数据方法
     */
    @Override
    public void write() {
        //1.获取写锁
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("rwLock");
        //2.执行写数据
        RLock rLock = rwLock.writeLock();
        rLock.lock(5, TimeUnit.SECONDS);
        System.out.println("执行写数据操作");
        //3.将锁释放

    }

    @Override
    @Async("customAsyncThreadPool")
    public void testAsync(String param) {
        log.info("任务执行了..,线程名称:{}," + param, Thread.currentThread().getName());
    }

}
